/*******************************************************************************
 * Copyright (c) 2006, 2014 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Markus Schorn - initial API and implementation
 *******************************************************************************/ 
package org.eclipse.cdt.core.internal.tests;

import java.util.Random;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.eclipse.cdt.internal.core.PositionTracker;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;

public class PositionTrackerTests extends TestCase {
    public static Test suite() {
        return new TestSuite(PositionTrackerTests.class);
    }

    public void testInitialFailures() {
        int[][] moves = {
            {46, -18, 95, -76, 98, -89, 10, -10, 85, -80, 16, 6, 5, -3, 
                22, -8, 29, -20, 86, -62, 34, -21, 63, -41, 9, 10, 18, -7},
            {0, 2,  1,-4},
            {4,-1,  0, 2, 0,-5},
            {0, 1,  2, 1, 0,-5},
            {0, 1,  2,-3, 1,-4},
            {4, 3,  3,-2, 0,-1},
            {4,-1,  3, 1, 2,-1},
            {0, 1,  2, 8, 1,-8, 0,-10},
            {4,-1,  2, 1, 4, 1, 0,-1, 0,-5},
        };
        int[] buffer = new int[100];
        for (int i = 0; i < moves.length; i++) {
            testMove(buffer, moves[i]);
        }
    }

    public void testRotations() {
        int[][] moves = { { 0, 1, 2, 1, 4, 1, 6, 1, 8, 1, 10, 1, 12, 1, 14, 1, 16, 1, 18, 1, 20, 1, 22, 1, 24, 1 }, {
                15, 1, 14, 1, 13, 1, 12, 1, 11, 1, 10, 1, 9, 1, 8, 1, 7, 1, 6, 1, 5, 1, 4, 1, 3, 1 }, {
                0, 1, 10, 1, 2, 1, 20, 1, 4, 1, 20, 1, 6, 1, 20, 1, 8, 1, 20, 1, 10, 1, 20, 1, 12, 1 }, };
        int[] buffer = new int[30];
        for (int i = 0; i < moves.length; i++) {
            assertTrue(testMove(buffer, moves[i]).depth() <= 5);
        }
    }

    public void testDepth4() {
        fullTest(5, 4);
    }

    public void testRandomDepth5() {
        randomTest(20, 5, 5, 1000);
    }

    public void testRandomDepth10() {
        randomTest(50, 10, 10, 1000);
    }

    public void testRandomDepth15() {
        randomTest(100, 15, 15, 1000);
    }

    public void testRandomDepth20() {
        randomTest(100, 15, 20, 1000);
    }

    public void testRetireDepth2() {
        randomRetireTest(100, 10, 25, 2, 1000);
    }

    public void testRetireDepth5() {
        randomRetireTest(100, 10, 10, 5, 1000);
    }

    public void testRetireDepth10() {
        randomRetireTest(100, 10, 5, 10, 1000);
    }

    public static void fullTest(int len, int depth) {
        // init buffer
        int[] buffer = new int[len];
        int[] move = new int[2 * depth];
        for (int i = 0; i < move.length; i++) {
            move[i] = -1;
        }
        while (nextMove(move, len)) {
            testMove(buffer, move);
        }
    }

    public static void randomTest(int buflen, int changelen, int depth, int count) {
        // init buffer
        Random rand = new Random();

        int[] buffer = new int[buflen];
        int[] move = new int[2 * depth];

        for (int j = 0; j < count; j++) {
            for (int i = 0; i < move.length; i += 2) {
                move[i] = rand.nextInt(buflen);
                move[i + 1] = rand.nextInt(2 * changelen) - changelen;
            }
            testMove(buffer, move);
        }
    }

    public static void randomRetireTest(int buflen, int changelen, int depth, int trackerDepth, int count) {
        // init buffer
        Random rand = new Random();

        int[] buffer = new int[buflen];
        int[] move = new int[2 * depth];

        for (int j = 0; j < count; j++) {
            for (int i = 0; i < buffer.length; i++) {
                buffer[i] = i;
            }

            PositionTracker t0 = null;
            PositionTracker previous = null;
            for (int t = 0; t < trackerDepth; t++) {
                for (int i = 0; i < move.length; i += 2) {
                    move[i] = rand.nextInt(buflen);
                    move[i + 1] = rand.nextInt(2 * changelen) - changelen;
                }
                PositionTracker tracker = new PositionTracker();
                if (previous != null) {
                    previous.retire(tracker);
                }
                doMove(buffer, move, tracker);
                if (t0 == null) {
                    t0 = tracker;
                }
                previous = tracker;
            }
            check(t0, buffer);
        }
    }

    static PositionTracker testMove(int[] buffer, int[] move) {
        try {
            return __testMove(buffer, move);
        } catch (RuntimeException e) {
            System.out.println("Error on move: ");  //$NON-NLS-1$
            for (int i = 0; i < move.length; i++) {
                System.out.print(move[i] + ", ");  //$NON-NLS-1$
            }
            System.out.println();
            throw e;
        } catch (Error e) {
            System.out.println("Error on move: ");  //$NON-NLS-1$
            for (int i = 0; i < move.length; i++) {
                System.out.print(move[i] + ", ");  //$NON-NLS-1$
            }
            System.out.println();
            throw e;
        }
    }

    static PositionTracker __testMove(int[] buffer, int[] move) {
        PositionTracker tracker = new PositionTracker();
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = i;
        }
        doMove(buffer, move, tracker);
        check(tracker, buffer);
        return tracker;
    }

    static void doMove(int[] buffer, int[] move, PositionTracker tracker) {
        for (int i = 0; i < move.length; i += 2) {
            int m1 = move[i];
            int m2 = move[i + 1];
            if (m1 == -1) {
                break;
            }
            if (m2 > 0) {
                tracker.insert(m1, m2);
                for (int j = 0; j < buffer.length; j++) {
                    if (buffer[j] >= m1) {
                        buffer[j] += m2;
                    }
                }
            } else {
                tracker.delete(m1, -m2);
                int m3 = m1 - m2;
                for (int j = 0; j < buffer.length; j++) {
                    if (buffer[j] >= m1) {
                        if (buffer[j] < m3) {
                            buffer[j] = -1;
                        } else {
                            buffer[j] += m2;
                        }
                    }
                }
            }
        }
    }

    private static void check(PositionTracker tracker, int[] buffer) {
        int lasti2 = -1;
        for (int i = 0; i < buffer.length; i++) {
            int i2 = buffer[i];
            if (i2 >= 0) {
                int i22 = tracker.currentOffset(i);
                assertEquals(i22, i2);
                assertTrue(lasti2 < i22);
                lasti2 = i22;

                assertEquals(i, tracker.historicOffset(i2));
            }
        }
    }

    private static boolean nextMove(int[] move, int bufLen) {
        for (int i = 0; i < move.length; i += 2) {
            int m1 = move[i];
            if (m1 < 0) {
                move[i] = 0;
                move[i + 1] = -bufLen;
                return true;
            }
            int m2 = ++move[i + 1];
            if (m2 <= bufLen - m1) {
                return true;
            }
            if (m1 < bufLen - 1) {
                move[i]++;
                move[i + 1] = -bufLen + m1 + 1;
                return true;
            }
            move[i] = 0;
            move[i + 1] = -bufLen;
        }
        return false;
    }
    
    public void testInsertion() {
        PositionTracker pt= new PositionTracker();
        pt.insert(1,1);
        
        checkInsert11(pt);
    }

    private void checkInsert11(PositionTracker pt) {
        // chars
        doubleCheck(pt, 0, 0);
        backwdCheck(pt, 1, 1);
        doubleCheck(pt, 1, 2);
        doubleCheck(pt, 2, 3);
        
        // ranges
        doubleRangeCheck(pt, new Region(0,2), new Region(0,3));
        backwdRangeCheck(pt, new Region(0,1), new Region(0,2));
        doubleRangeCheck(pt, new Region(0,1), new Region(0,1));
        backwdRangeCheck(pt, new Region(1,0), new Region(1,1));
        backwdRangeCheck(pt, new Region(1,0), new Region(1,0));
        doubleRangeCheck(pt, new Region(1,1), new Region(2,1));
        doubleRangeCheck(pt, new Region(1,0), new Region(2,0));
    }

    public void testDeletion() {
        PositionTracker pt= new PositionTracker();
        pt.delete(1,1);
        checkDelete11(pt);
    }

    private void checkDelete11(PositionTracker pt) {
        doubleCheck(pt, 0, 0);
        fwdCheck   (pt, 1, 1);
        doubleCheck(pt, 2, 1);
        doubleCheck(pt, 3, 2);

        // ranges
        doubleRangeCheck(pt, new Region(0,3), new Region(0,2));
        fwdRangeCheck   (pt, new Region(0,2), new Region(0,1));
        doubleRangeCheck(pt, new Region(0,1), new Region(0,1));
        fwdRangeCheck   (pt, new Region(1,1), new Region(1,0));
        fwdRangeCheck   (pt, new Region(1,0), new Region(1,0));
        doubleRangeCheck(pt, new Region(2,1), new Region(1,1));
        doubleRangeCheck(pt, new Region(2,0), new Region(1,0));
    }
    
    public void testReplace() {
        PositionTracker pt= new PositionTracker();
        pt.delete(1,1);
        pt.insert(1,1);
        doubleCheck(pt, 0, 0);
        doubleCheck(pt, 1, 1);
        doubleCheck(pt, 2, 2);
        doubleCheck(pt, 3, 3);

        pt.clear();
        pt.insert(1,1);
        pt.delete(1,1);
        doubleCheck(pt, 0, 0);
        doubleCheck(pt, 1, 1);
        doubleCheck(pt, 2, 2);
        doubleCheck(pt, 3, 3);
        
        pt.clear();
        pt.delete(0,2);
        pt.insert(0,1);
        checkDelete11(pt);

        pt.clear();
        pt.insert(1,1);
        pt.delete(1,2);
        checkDelete11(pt);

        pt.clear();
        pt.insert(1,2);
        pt.delete(1,1);
        checkInsert11(pt);

        pt.clear();
        pt.delete(1,1);
        pt.insert(1,2);
        checkInsert11(pt);
    }       

    private void doubleCheck(PositionTracker pt, int orig, int mapped) {
        fwdCheck(pt, orig, mapped);
        backwdCheck(pt, orig, mapped); 
    }

    private void fwdCheck(PositionTracker pt, int orig, int mapped) {
        assertEquals(mapped, pt.currentOffset(orig));
    }

    private void backwdCheck(PositionTracker pt, int orig, int mapped) {
        assertEquals(orig, pt.historicOffset(mapped));
    }
    
    private void doubleRangeCheck(PositionTracker pt, IRegion orig, IRegion mapped) {
        fwdRangeCheck(pt, orig, mapped);
        backwdRangeCheck(pt, orig, mapped);         
    }

    private void fwdRangeCheck(PositionTracker pt, IRegion orig, IRegion mapped) {
        assertEquals(mapped, pt.historicToActual(orig));
    }

    private void backwdRangeCheck(PositionTracker pt, IRegion orig, IRegion mapped) {
        assertEquals(orig, pt.actualToHistoric(mapped));
    }
}
